package com.dj.mingkong.common.util;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public enum ReflectUtils {
    ;
    private static final Logger log = LoggerFactory.getLogger(ReflectUtils.class);
    private static final String CACHE_FIELD_KEY_FORMAT = "%s.%s";
    private static final Cache<String, Field> CACHE = Caffeine.newBuilder().maximumSize(1024L).expireAfterWrite(5L, TimeUnit.MINUTES).weakKeys().weakValues().removalListener(new RemovalListener<String, Field>() {
        public void onRemoval(@Nullable String key, @Nullable Field value, @Nullable RemovalCause cause) {
            if (value != null) {
                ReflectUtils.log.info("key:{} , value: {}, 删除原因:{}", new Object[]{key, value.getName(), cause});
            } else {
                ReflectUtils.log.info("key:{} , 删除原因:{}", key, cause);
            }

        }
    }).build();

    private ReflectUtils() {
    }

    public static List<Field> getAllFields(Class<?> clz) {
        ArrayList fields;
        for (fields = new ArrayList(); !Object.class.equals(clz); clz = clz.getSuperclass()) {
            Field[] declaredFields = clz.getDeclaredFields();
            Collections.addAll(fields, declaredFields);
        }

        return fields;
    }

    public static Field getField(Class<?> clz, String fieldName) throws NoSuchFieldException {
        AssertUtils.isNotNull(clz, "clz must not be null", new Object[0]);
        AssertUtils.isNotBlank(fieldName, "fieldName must not be blank", new Object[0]);
        String cacheKey = String.format("%s.%s", clz.getCanonicalName(), fieldName);
        return (Field) CACHE.get(cacheKey, (key) -> {
            try {
                return getField0(clz, fieldName);
            } catch (NoSuchFieldException var4) {
                log.error("parse field error! clz:{}, fieldName:{}", new Object[]{clz.getName(), fieldName, var4});
                return null;
            }
        });
    }

    private static Field getField0(Class<?> clz, String fieldName) throws NoSuchFieldException {
        Field declaredField = null;

        Class orgClz;
        for (orgClz = clz; !Object.class.equals(clz); clz = clz.getSuperclass()) {
            try {
                declaredField = clz.getDeclaredField(fieldName);
            } catch (NoSuchFieldException var5) {
            }
        }

        if (declaredField == null) {
            throw new NoSuchFieldException("查找class:" + orgClz.getName() + "的field:" + fieldName + " 失败");
        } else {
            return declaredField;
        }
    }

    public static Object getFieldValue(Object obj, String fieldName) {
        AssertUtils.isNotNull(obj, "obj must not be null", new Object[0]);
        AssertUtils.isNotBlank(fieldName, "fieldName must not be blank", new Object[0]);
        Field field = getAccessibleField(obj, fieldName);
        if (field == null) {
            return null;
        } else {
            try {
                return field.get(obj);
            } catch (IllegalAccessException var4) {
                log.error("此Field" + fieldName + "对象" + obj + "实施Java语言访问控制和基础字段是不可访问。", var4);
                return null;
            }
        }
    }

    public static Field getAccessibleField(Object obj, String fieldName) {
        AssertUtils.isNotNull(obj, "obj must not be null", new Object[0]);
        AssertUtils.isNotBlank(fieldName, "fieldName must not be blank", new Object[0]);
        String cacheKey = String.format("%s.%s", obj.getClass().getCanonicalName(), fieldName);
        return (Field) CACHE.get(cacheKey, (key) -> {
            return getAccessibleField0(obj, fieldName);
        });
    }

    private static Field getAccessibleField0(Object obj, String fieldName) {
        Class superClass = obj.getClass();

        while (superClass != Object.class) {
            try {
                Field field = superClass.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field;
            } catch (NoSuchFieldException var4) {
                if (!Object.class.isAssignableFrom(superClass)) {
                    log.error("", var4);
                } else {
                    log.debug("obj {} has not this filed:{}, will find in its parent class.", obj.getClass().getSimpleName(), fieldName);
                }

                superClass = superClass.getSuperclass();
            }
        }

        return null;
    }

    public static Class getSuperclassGenericType(Class clazz, int index) {
        AssertUtils.isNotNull(clazz, "obj must not be null", new Object[0]);
        AssertUtils.isNatureNumber(index, "index must greater than zero", new Object[0]);
        Type genType = clazz.getGenericSuperclass();
        if (!(genType instanceof ParameterizedType)) {
            log.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
            return null;
        } else {
            Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
            if (index < params.length && index >= 0) {
                if (!(params[index] instanceof Class)) {
                    log.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
                    return Object.class;
                } else {
                    return (Class) params[index];
                }
            } else {
                log.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + params.length);
                return null;
            }
        }
    }

    public static <T> Class<T> getSuperClassFirstGenericType(Class<T> clazz) {
        return getSuperclassGenericType(clazz, 0);
    }

    public static Object invokeGetterMethod(Object obj, String propertyName) {
        String getterMethodName = "get" + StringUtils.capitalize(propertyName);
        return invokeMethod(obj, getterMethodName, new Class[0], new Object[0]);
    }

    public static void invokeSetterMethod(Object obj, String propertyName, Object value) {
        invokeSetterMethod(obj, propertyName, value, (Class) null);
    }

    public static void invokeSetterMethod(Object obj, String propertyName, Object value, Class<?> propertyType) {
        Class<?> type = propertyType != null ? propertyType : value.getClass();
        String setterMethodName = "set" + StringUtils.capitalize(propertyName);
        invokeMethod(obj, setterMethodName, new Class[]{type}, new Object[]{value});
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) {
        Field field = getAccessibleField(obj, fieldName);
        if (field == null) {
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
        } else {
            try {
                field.set(obj, value);
            } catch (IllegalAccessException var5) {
                log.error("不可能抛出的异常:{}", var5.getMessage());
            }

        }
    }

    public static Object invokeMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object[] args) {
        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
        if (method == null) {
            throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
        } else {
            try {
                return method.invoke(obj, args);
            } catch (Exception var6) {
                throw convertReflectionExceptionToUnchecked(var6);
            }
        }
    }

    public static Method getAccessibleMethod(Object obj, String methodName, Class<?>... parameterTypes) {
        if (obj == null) {
            log.error("ReflectionUtil.getAccessibleField(final Object obj, final String property)的obj参数不能为空");
            throw new NullPointerException("object不能为空");
        } else {
            Class superClass = obj.getClass();

            while (superClass != Object.class) {
                try {
                    Method method = superClass.getDeclaredMethod(methodName, parameterTypes);
                    method.setAccessible(true);
                    return method;
                } catch (NoSuchMethodException var5) {
                    log.error("NoSuchMethodException!", var5);
                    superClass = superClass.getSuperclass();
                }
            }

            return null;
        }
    }

    public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
        if (!(e instanceof IllegalAccessException) && !(e instanceof IllegalArgumentException) && !(e instanceof NoSuchMethodException)) {
            if (e instanceof InvocationTargetException) {
                return new RuntimeException("Reflection Exception.", ((InvocationTargetException) e).getTargetException());
            } else {
                return e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException("Unexpected Checked Exception.", e);
            }
        } else {
            return new IllegalArgumentException("Reflection Exception.", e);
        }
    }

    public static Map<String, Object> getStaticField(Class clazz) throws Exception {
        Map<String, Object> fieldMap = Maps.newHashMap();
        Field[] fields = clazz.getDeclaredFields();
        if (fields.length <= 0) {
            return fieldMap;
        } else {
            Field[] var3 = fields;
            int var4 = fields.length;

            for (int var5 = 0; var5 < var4; ++var5) {
                Field field = var3[var5];
                field.setAccessible(true);
                if (Modifier.isStatic(field.getModifiers())) {
                    fieldMap.put(field.getName(), field.get(clazz));
                }
            }

            return fieldMap;
        }
    }

    public static Object getStaticField(Class clazz, String staticFieldName) {
        Object obj = null;
        Field[] fields = clazz.getDeclaredFields();
        if (fields.length <= 0) {
            return null;
        } else {
            try {
                Field[] var4 = fields;
                int var5 = fields.length;

                for (int var6 = 0; var6 < var5; ++var6) {
                    Field field = var4[var6];
                    field.setAccessible(true);
                    String fieldName = field.getName();
                    if (Modifier.isStatic(field.getModifiers()) && fieldName.equals(staticFieldName)) {
                        obj = field.get(clazz);
                    }
                }
            } catch (Exception var9) {
                obj = null;
            }

            return obj;
        }
    }
}
